﻿/*******************************************************************************************
 * Copyright (c) 2006-2009 Alex Robson, Rob Simmons, Craig Israel, and Rachel Twyford
 *
 * Use of this software in any form requires that you have both 
 * read and agreed to the following terms:
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions: 
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the 
 * documentation and/or other materials provided with the distribution.
 * Neither the name of Nvigorate nor the names of its contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************************/

using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using System.Text;
using Nvigorate.Extensions;

namespace Nvigorate.Common.Reflection
{
    public sealed partial class Reflector
    {
        #region Internal

        internal static Dictionary<Type, Func<object, object>> _cloneFunctions = new Dictionary<Type, Func<object, object>>()
         {
                {typeof(ICloneable), CloneICloneable },
                {typeof(XElement), CloneXElement },
                {typeof(XNode), CloneXNode },
                {typeof(ValueType), (o) => o},
                {typeof(object), (o) => Clone(o)},
                {typeof(string), CloneString},
                { typeof (IList), CloneIList },
                { typeof (IDictionary), CloneIDictionary },

         };

        internal static Func<Type, Type> getCloneBase = (o) =>
        {
            if (HasBaseTypeOf(o, typeof(string)))
                return typeof(string);
            else if (HasBaseTypeOf(o, typeof(XNode)))
                return typeof(XNode);
            else if (HasBaseTypeOf(o, typeof(XElement)))
                return typeof(XElement);
            else if (ImplementsInterface(o, cloneType))
                return cloneType;
            else if (ImplementsInterface(o, listType))
                return listType;
            else if (ImplementsInterface(o, dictionaryType))
                return dictionaryType;
            else if (o.IsValueType)
                return typeof(ValueType);
            else
                return typeof(object);
        };

        #endregion
        
        #region Internal Factory Methods

        internal static object CloneICloneable(object value)
        {
            return value == null ? null : ((ICloneable)value).Clone();
        }

        internal static object CloneIDictionary(object value)
        {
            if (value == null)
                return null;

            Type targetType = value.GetType();
            IDictionary oldDictionary = (IDictionary)value;
            IDictionary newDictionary = (IDictionary)Activator.CreateInstance(targetType);
            foreach (var key in oldDictionary.Keys)
            {
                newDictionary.Add(
                        Clone(key),
                        Clone(oldDictionary[key])
                    );
            }
            return newDictionary;
        }

        internal static object CloneIList(object value)
        {
            if (value == null)
                return null;

            Type targetType = value.GetType();
            IList oldList = (IList)value;
            IList newList = (IList)Activator.CreateInstance(targetType);
            foreach (var item in oldList)
            {
                newList.Add(Clone(item));
            }
            return newList;
        }

        internal static object CloneString(object value)
        {
            return value == null ? null : String.Copy((string)value);
        }

        internal static object CloneXNode(object value)
        {
            if (value == null)
                return null;
            return CallMethod(value, "CloneNode");
        }

        internal static object CloneXElement(object value)
        {
            if (value == null)
                return null;

            XElement oldElement = (XElement)value;
            XElement newElement = XElement.Parse(oldElement.ToString());
            return newElement;
        }

        #endregion

        #region DeepClone

        /// <summary>
        /// This is a deep-cloning function which uses reflection to clone an entire object tree
        /// </summary>
        /// <typeparam name="T">The type of object to clone</typeparam>
        /// <param name="target">The instance to create a clone of</param>
        /// <returns>An identical object tree</returns>
        public static T Clone<T>(T target)
        {
            if (((object)target) == null)
                return default(T);

            Type targetType = target.GetType();
            T newInstance = default(T);

            //If T is ICloneable, we should just be using the type's Clone
            //method anyway
            if (ImplementsInterface(targetType, cloneType))
            {
                return (T)((ICloneable)target).Clone();
            }

            //If T is an XNode, we should just be using the type's CloneNode method
            if (HasBaseTypeOf(targetType, typeof(XNode)))
            {
                return (T)_cloneFunctions[typeof(XNode)](target);
            }

            //Otherwise, check to see if T should be straight cloned
            //from one of the clone functions or if we need to use
            //CopyObject to copy all the values to a new instance
            if (ImplementsInterface(targetType, listType))
            {
                newInstance = (T)_cloneFunctions[listType](target);
            }
            else if (ImplementsInterface(targetType, dictionaryType))
            {
                newInstance = (T)_cloneFunctions[dictionaryType](target);
            }
            else if (HasDefaultConstructor(targetType))
            {
                newInstance = (T)Activator.CreateInstance(target.GetType());
                CopyObject(newInstance, target);
            }
            else
            {
                ConstructorInfo constructor = GetCopyConstructor(targetType);
                if (constructor != null)
                {
                    object param = target;
                    newInstance = (T)constructor.Invoke(new[] { param });
                }
                else
                {
                    //We surrender. Someone obviously wanted to make it difficult or
                    //impossible to clone this type so we'll just copy the reference.
                    //Sorry Microsoft, that's what you get for breaking your own
                    //best practice : p
                    newInstance = target;
                }
            }

            return newInstance;
        }

        /// <summary>
        /// This is a deep-copy operation which uses reflection to copy an object tree to another instance
        /// </summary>
        /// <typeparam name="T">The type of object to copy</typeparam>
        /// <param name="target">The instance to create a copy of</param>
        /// <param name="source">The instance to copy into</param>
        public static void CopyObject<T>(T target, T source)
        {
            Type targetType = target.GetType();

            bool tempBool = SearchHierarchy;

            SearchHierarchy = true;

            GetFieldInfoList(targetType)
                .Where(f => !f.IsLiteral)
                .ForEach(f => cache.Write(
                                    target,
                                    f.Name,
                                    _cloneFunctions[getCloneBase(f.FieldType)](f.GetValue(source))));

            SearchHierarchy = tempBool;

        }

        #endregion

        #region DeepCompare

        /// <summary>
        /// This is a deep-compare operation which uses reflection to determine if two instances are equal
        /// by comparing property values
        /// </summary>
        /// <typeparam name="T">The type of the objects to compare</typeparam>
        /// <param name="instance1">The instance to compare against</param>
        /// <param name="instance2">The instance to compare</param>
        /// <returns>True if instances are equal, false if not</returns>
        public static bool AreEqualValue<T>(T instance1, T instance2)
        {
            return AreEqualValue(instance1, instance2, GetProperties(typeof(T)));
        }

        /// <summary>
        /// This is a deep-compare operation which uses reflection to determine if two instances are equal
        /// by comparing specific property values
        /// </summary>
        /// <typeparam name="T">The type of the objects to compare</typeparam>
        /// <param name="instance1">The instance to compare against</param>
        /// <param name="instance2">The instance to compare</param>
        /// <returns>True if instances are equal, false if not</returns>
        public static bool AreEqualValue<T>(T instance1, T instance2, List<string> properties)
        {
            Type type = typeof(T);

            foreach (string property in properties)
            {
                if (!HasProperty(type, property))
                    return false;

                var instance1PropertyValue = Read(instance1, property);
                var instance2PropertyValue = Read(instance2, property);

                if (IsDictionary(GetPropertyInfo(type, property, true).PropertyType))
                {
                    IDictionary dictionary1 = (IDictionary)instance1PropertyValue;
                    IDictionary dictionary2 = (IDictionary)instance2PropertyValue;

                    foreach (DictionaryEntry entry in dictionary1)
                    {
                        if (dictionary2.Contains(entry.Key))
                        {
                            if (!dictionary2[entry.Key].Equals(entry.Value))
                                return false;
                        }
                        else
                        {
                            return false;
                        }
                    }
                }
                else if (IsEnumerable(GetPropertyInfo(type, property, true).PropertyType))
                {
                    IEnumerable enum1 = (IEnumerable)instance1PropertyValue;
                    IEnumerable enum2 = (IEnumerable)instance2PropertyValue;

                    

                    IList list1 = enum1 as IList;
                    IList list2 = enum2 as IList;

                    if (list1 == null)
                    {
                        ICollection collection1 = enum1 as ICollection;
                        ICollection collection2 = enum2 as ICollection;

                        if(collection1 == null && collection2 == null)
                        {
                            return true;
                        }
                        else if(collection1 == null)
                        {
                            
                        }

                        if (collection1.Count != collection2.Count)
                            return false;

                        foreach (object value in collection1)
                        {
                            bool found = false;
                            foreach (object value2 in collection2)
                            {
                                if (value2.Equals(value))
                                    found = true;
                            }
                            if (!found)
                                return false;
                        }
                    }
                    else
                    {
                        if (list1.Count != list2.Count)
                            return false;
                        foreach (object value in list1)
                        {
                            if (!list2.Contains(value))
                                return false;
                        }
                    }
                }
                else
                {
                    if ((instance1PropertyValue == null && instance2PropertyValue != null) ||
                        (instance1PropertyValue != null && !instance1PropertyValue.Equals(instance2PropertyValue)))
                        return false;
                }
            }
            return true;
        }

        #endregion
    }
}
